package cn.hbads.support.date;

import cn.hbads.support.error.CommonErrorType;
import cn.hbads.support.error.ErrorType;
import cn.hbads.support.structure.result.ComputeResult;
import org.apache.commons.lang3.time.DateUtils;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;

/**
 * @author yvvb
 * @date 2025/4/16
 * @apiNote 日期工具类
 */
public final class DateUtil {

    // 业务上的最大日期
    public static final LocalDate MAX_DATE = LocalDate.of(9999, 12, 31);
    // 业务上的最小日期
    public static final LocalDate MIN_DATE = LocalDate.of(1000, 1, 1);

    private DateUtil() {
    }

    /**
     * 获取当前日期的Date对象
     *
     * @retrun {@link Date} 当前日期的Date对象
     */
    public static Date nowDay() {
        return DateUtils.truncate(new Date(), Calendar.DATE);
    }

    /**
     * 判断两个Date对象是否在同一日期
     *
     * @param date1 {@link Date} 日期1
     * @param date2 {@link Date} 日期2
     *
     * @retrun boolean 是否同一天
     */
    public static boolean isSameDay(Date date1, Date date2) {
        return DateUtils.truncatedEquals(date1, date2, Calendar.DATE);
    }

    /**
     * @param source {@link Long} 毫秒数
     *
     * @return {@link LocalDateTime} 时间
     *
     * @apiNote Long转LocalDateTime工具方法
     */
    public static LocalDateTime longToLocalDateTime(long source) {
        return Instant.ofEpochMilli(source).atZone(ZoneId.systemDefault()).toLocalDateTime();
    }

    /**
     * 获取传入Date的前一天的Date对象
     *
     * @param date {@link Date} 传入时间对象
     *
     * @retrun {@link Date} 前一天的Date对象
     */
    public static Date yesterday(Date date) {
        return DateUtils.addDays(DateUtils.truncate(date, Calendar.DATE), -1);
    }

    /**
     * 获取传入Date的后一天的Date对象
     *
     * @param date {@link Date} 传入时间对象
     *
     * @retrun {@link Date} 后一天的Date对象
     */
    public static Date tomorrow(Date date) {
        return DateUtils.addDays(DateUtils.truncate(date, Calendar.DATE), 1);
    }


    /**
     * @param time {@link LocalDateTime} 时间
     *
     * @return {@link Long} 毫秒数
     *
     * @apiNote LocalDateTime转Long工具方法
     */
    public static Long localDateTimeToLong(LocalDateTime time) {
        return time.toInstant(ZoneId.systemDefault().getRules().getOffset(time)).toEpochMilli();
    }

    /**
     * @param temporal {@link TemporalAccessor} 需要转化的日期时间
     * @param pattern  {@link String}时间格式
     *
     * @return {@link String} 日期时间字符串
     *
     * @apiNote 格式化日期时间工具方法
     */
    public static ComputeResult<String, ErrorType> format(TemporalAccessor temporal, String pattern) {
        try {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
            return ComputeResult.success(dateTimeFormatter.format(temporal));
        } catch (DateTimeException | IllegalArgumentException exception) {
            return ComputeResult.fail(CommonErrorType.FORMAT_TEMPORAL_ERROR, exception);
        }
    }

    /**
     * @param pattern {@link String}时间格式
     *
     * @return {@link  ComputeResult} 日期时间字符串封装结果
     *
     * @apiNote 格式化当前时间工具方法
     */
    public static ComputeResult<String, ErrorType> formatDateTimeNow(String pattern) {
        return format(LocalDateTime.now(), pattern);
    }

    /**
     * @param pattern {@link String}时间格式
     *
     * @return {@link  ComputeResult} 日期时间字符串封装结果
     *
     * @apiNote 格式化当前日期工具方法
     */
    public static ComputeResult<String, ErrorType> formatDateNow(String pattern) {
        return format(LocalDate.now(), pattern);
    }

    /**
     * @param localDateTimeStr {@link String} 日期时间字符串
     * @param pattern          {@link String}时间格式
     *
     * @return {@link LocalDateTime} 解析得到的时间
     *
     * @apiNote 从字符串解析LocalDateTime的工具方法
     */
    public static ComputeResult<LocalDateTime, ErrorType> parseDateTime(String localDateTimeStr, String pattern) {
        try {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
            return ComputeResult.success(LocalDateTime.parse(localDateTimeStr, dateTimeFormatter));
        } catch (DateTimeParseException | IllegalArgumentException exception) {
            return ComputeResult.fail(CommonErrorType.PARSE_STR_TO_TEMPORAL_ERROR, exception);
        }
    }

    /**
     * @param localDateStr {@link String} 日期时间字符串
     * @param pattern      {@link String}时间格式
     *
     * @return {@link LocalDate} 解析得到的日期
     *
     * @apiNote 从字符串解析LocalDate的工具方法
     */
    public static ComputeResult<LocalDate, ErrorType> parseDate(String localDateStr, String pattern) {
        try {
            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
            return ComputeResult.success(LocalDate.parse(localDateStr, dateTimeFormatter));
        } catch (DateTimeParseException | IllegalArgumentException exception) {
            return ComputeResult.fail(CommonErrorType.PARSE_STR_TO_TEMPORAL_ERROR, exception);
        }
    }

    /**
     * @param localDate1 {@link LocalDate} 日期1
     * @param localDate2 {@link LocalDate} 日期2
     *
     * @return 判断结果 {@link  Boolean }
     *
     * @apiNote 判断两个日期是否为同一天(param @ Nullable)
     */
    public static boolean isSameDay(LocalDate localDate1, LocalDate localDate2) {
        if (Objects.isNull(localDate1) || Objects.isNull(localDate2)) {
            return false;
        }
        return localDate1.equals(localDate2);
    }

    /**
     * @param localDate1 {@link LocalDate} 日期1
     * @param localDate2 {@link LocalDate} 日期2
     *
     * @return 判断结果 {@link  Boolean}
     *
     * @apiNote 判断日期1不晚于日期2(param @ Nullable)
     */
    public static Boolean noAfter(LocalDate localDate1, LocalDate localDate2) {
        if (Objects.isNull(localDate1) || Objects.isNull(localDate2)) {
            return false;
        }
        return !localDate1.isAfter(localDate2);
    }

    /**
     * @param localDate1 {@link LocalDate} 日期1
     * @param localDate2 {@link LocalDate} 日期2
     *
     * @return 判断结果 {@link  Boolean}
     *
     * @apiNote 判断日期1不早于日期2(param @ Nullable)
     */
    public static Boolean noBefore(LocalDate localDate1, LocalDate localDate2) {
        if (Objects.isNull(localDate1) || Objects.isNull(localDate2)) {
            return false;
        }
        return !localDate1.isBefore(localDate2);
    }


    /**
     * @param date1 {@link LocalDate} 日期1
     * @param date1 {@link LocalDate} 日期2
     *
     * @return 判断结果 {@link  Boolean}
     *
     * @apiNote 判断两个日期，参数一是否在参数二之前
     */
    public static Boolean isBefore(LocalDate date1, LocalDate date2) {
        if (Objects.isNull(date1) || Objects.isNull(date2)) {
            return false;
        }
        return date1.isBefore(date2);
    }

    /**
     * @param date1 {@link LocalDate} 日期1
     * @param date1 {@link LocalDate} 日期2
     *
     * @return 判断结果 {@link  Boolean}
     *
     * @apiNote 判断两个日期，参数一是否在参数二之后
     */
    public static Boolean isAfter(LocalDate date1, LocalDate date2) {
        if (Objects.isNull(date1) || Objects.isNull(date2)) {
            return false;
        }
        return date1.isAfter(date2);
    }

    public static Boolean isNextDay(LocalDate localDate1, LocalDate localDate2) {
        return isSameDay(tomorrow(localDate1), localDate2);
    }

    /**
     * @param dateTime1 {@link LocalDateTime} 时间1
     * @param dateTime2 {@link LocalDateTime} 时间2
     *
     * @return 判断结果 {@link  Boolean}
     *
     * @apiNote 判断两个时间是否为同一天(param @ Nullable)
     */
    public static Boolean isSameDay(LocalDateTime dateTime1, LocalDateTime dateTime2) {
        if (Objects.isNull(dateTime1) || Objects.isNull(dateTime2)) {
            return false;
        }
        return dateTime1.toLocalDate().equals(dateTime2.toLocalDate());
    }

    /**
     * @param dateTime1 {@link LocalDateTime} 时间1
     * @param dateTime2 {@link LocalDateTime} 时间2
     * @param unit      {@link ChronoUnit} 单位
     *
     * @return 判断结果 {@link  Boolean}
     *
     * @apiNote 判断两个时间截断到指定时间单位是否相同
     */
    public static Boolean truncateEquals(LocalDateTime dateTime1, LocalDateTime dateTime2, ChronoUnit unit) {
        if (Objects.isNull(dateTime1) || Objects.isNull(dateTime2)) {
            return false;
        }
        return dateTime1.truncatedTo(unit).equals(dateTime2.truncatedTo(unit));
    }

    /**
     * @param dateTime1 {@link LocalDateTime} 时间1
     * @param dateTime2 {@link LocalDateTime} 时间2
     * @param unit      {@link ChronoUnit} 单位
     *
     * @return 判断结果 {@link  Boolean}
     *
     * @apiNote 判断两个时间截断到指定时间单位，参数一是否在参数二之前
     */
    public static Boolean truncateBefore(LocalDateTime dateTime1, LocalDateTime dateTime2, ChronoUnit unit) {
        if (Objects.isNull(dateTime1) || Objects.isNull(dateTime2)) {
            return false;
        }
        return dateTime1.truncatedTo(unit).isBefore(dateTime2.truncatedTo(unit));
    }

    /**
     * @param dateTime1 {@link LocalDateTime} 时间1
     * @param dateTime2 {@link LocalDateTime} 时间2
     * @param unit      {@link ChronoUnit} 单位
     *
     * @return 判断结果 {@link  Boolean}
     *
     * @apiNote 判断两个时间截断到指定时间单位，参数一是否在参数二之后
     */
    public static Boolean truncateAfter(LocalDateTime dateTime1, LocalDateTime dateTime2, ChronoUnit unit) {
        if (Objects.isNull(dateTime1) || Objects.isNull(dateTime2)) {
            return false;
        }
        return dateTime1.truncatedTo(unit).isAfter(dateTime2.truncatedTo(unit));
    }


    /**
     * @param date      {@link LocalDate} 操作日期
     * @param daysToAdd {@link Integer} 要增加的日期
     *
     * @return 增加后的日期 {@link  LocalDate}
     *
     * @apiNote 安全地对日期进行添加操作
     */
    public static LocalDate safePlusDays(LocalDate date, Integer daysToAdd) {
        if (Objects.isNull(date) || MAX_DATE.equals(date)) {
            return date;
        }
        return date.plusDays(daysToAdd);
    }

    public static LocalDate tomorrow(LocalDate date) {
        if (Objects.isNull(date) || MAX_DATE.equals(date)) {
            return date;
        }
        return date.plusDays(1);
    }

    public static LocalDate yesterday(LocalDate date) {
        if (Objects.isNull(date) || MIN_DATE.equals(date)) {
            return date;
        }
        return date.minusDays(1);
    }

    /**
     * @param date        {@link LocalDate} 操作日期
     * @param daysToMinus {@link Integer} 要减少的日期
     *
     * @return 减少后的日期 {@link  LocalDate}
     *
     * @apiNote 安全地对日期进行减少操作
     */
    public static LocalDate safeMinusDays(LocalDate date, Integer daysToMinus) {
        if (Objects.isNull(date) || MIN_DATE.equals(date)) {
            return date;
        }
        return date.minusDays(daysToMinus);
    }

    /**
     * @param localDate {@link LocalDate} 要转换的LocalDate对象
     *
     * @return 转换后的日期对象 {@link Date}
     *
     * @apiNote <b>将LocalDate对象转换为Date</b>
     * <br>从localDate的atStartOfDay方法转到LocalDateTime再加上时区转到Date
     */
    public static Date localDateToDate(LocalDate localDate) {
        if (Objects.isNull(localDate)) {
            return null;
        }
        return Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * @param dateTime {@link LocalDateTime} 要转换的LocalDateTime对象
     *
     * @return 转换后的日期对象 {@link Date}
     *
     * @apiNote <b>将LocalDateTime对象转换为Date</b>
     * <br>LocalDateTime再加上时区转为ZoneDateTime,再用toInstant转到Date
     */
    public static Date localDateTimeToDate(LocalDateTime dateTime) {
        if (Objects.isNull(dateTime)) {
            return null;
        }
        return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * @param date {@link Date} 要转换的Date对象
     *
     * @return 转换后的日期对象 {@link LocalDateTime}
     *
     * @apiNote <b>将Date对象转换为LocalDateTime对象</b>
     */
    public static LocalDateTime dateToLocalDateTime(Date date) {
        if (Objects.isNull(date)) {
            return null;
        }
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    }

    /**
     * @param date {@link Date} 要转换的Date对象
     *
     * @return 转换后的日期对象 {@link LocalDate}
     *
     * @apiNote <b>将Date对象转换为LocalDate对象</b>
     * <br/> 注意会截断精度到DAY
     */
    public static LocalDate dateToLocalDate(Date date) {
        if (Objects.isNull(date)) {
            return null;
        }
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    }

}
